﻿using System;
using System.Text;
using ChessKit.Common;

namespace ChessKit.ChessLogics
{
  public class Board
  {
    public static readonly Board StartPosition = FromFenString(
      "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1");

    private CompactBoard _cells = new CompactBoard(0);
    private BoardInfo _info;

    public Board Prev { get; private set; }
    public Move PrevMove { get; private set; }

    public PieceColor SideOnMove
    {
      get { return _info.SideOnMove; }
    }

    public IPiece GetPieceAt(Position position)
    {
      return Piece.Unpack(_cells[Pos.Pack(position)]);
    }

    #region ' MakeMove '

    public Board MakeMove(Move move)
    {
      return new Board(this, move);
    }

    private Board(Board src, Move move)
    {
      var progress = ValidationProgress.InProgress;
      var warnins = ValidationWarning.None;
      var error = ValidationError.None;
      var hint = MoveHint.None;

      try
      {
        // Piece in the from cell?
        var from = Pos.Pack(move.From);
        var piece = src._cells[from];
        if (piece == CompactPiece.EmptyCell)
        {
          progress = ValidationProgress.Invalid;
          error = ValidationError.EmptyCell;
          return;
        }

        // Side to move?
        var color = Piece.Color(piece);
        if (color != src._info.SideOnMove)
          warnins |= ValidationWarning.WrongSideToMove;

        // Move to occupied cell?
        var to = Pos.Pack(move.To);
        var toPiece = src._cells[to];
        if (toPiece != CompactPiece.EmptyCell &&
            Piece.Color(toPiece) == color)
        {
          progress = ValidationProgress.Invalid;
          error = ValidationError.OccupiedCell;
          return;
        }

        switch (Piece.Type(piece))
        {
          case PieceType.Pawn:
            var pawnMove = src._cells.IsValidPawnMove(color, from, to);
            if ((pawnMove & PawnMoveValidation.Valid) == 0)
            {
              progress = ValidationProgress.Invalid;
              error = (ValidationError)pawnMove;
              return;
            }
            hint = (MoveHint)pawnMove;
            switch (pawnMove)
            {
              case PawnMoveValidation.EnPassant:
                var notExpected = !src._info.IsEnPassant || src._info.EnPassant != to % 8;
                var wrongSideToMove = src._info.SideOnMove != color;
                var firstMove = src._info.HalfmoveClock == 0;
                if (wrongSideToMove || (!firstMove && notExpected))
                {
                  progress = ValidationProgress.Invalid;
                  error = ValidationError.PieceDoesntMoveThisWay;
                  return;
                }
                if (firstMove && notExpected)
                {
                  src._info.EnPassant = to % 8;
                }
                break;
              case PawnMoveValidation.Promotion:
              case PawnMoveValidation.PromotionWithTake:
                piece = (CompactPiece)move.ProposedPromotion;
                if (color == PieceColor.Black)
                  piece |= CompactPiece.IsBlack;
                break;
            }
            break;
          case PieceType.Bishop:
            var bishopMove = src._cells.IsValidBishopMove(from, to);
            if (bishopMove != SlideMoveValidation.Valid)
            {
              progress = ValidationProgress.Invalid;
              error = (ValidationError)bishopMove;
              return;
            }
            break;
          case PieceType.Knight:
            if (!src._cells.IsValidKnightMove(from, to))
            {
              progress = ValidationProgress.Invalid;
              error = ValidationError.PieceDoesntMoveThisWay;
              return;
            }
            break;
          case PieceType.Rook:
            hint = MoveHint.RookMove;
            var rookMove = src._cells.IsValidRookMove(from, to);
            if (rookMove != SlideMoveValidation.Valid)
            {
              progress = ValidationProgress.Invalid;
              error = (ValidationError)rookMove;
              return;
            }
            break;
          case PieceType.Queen:
            var b = src._cells.IsValidBishopMove(from, to);
            var r = src._cells.IsValidRookMove(from, to);
            if (b != SlideMoveValidation.Valid && 
                r != SlideMoveValidation.Valid)
            {
              progress = ValidationProgress.Invalid;
              error = (ValidationError)b;
              return;
            }
            break;
          case PieceType.King:
            var kingMove = src._cells.IsValidKingMove(from, to);
            if ((kingMove & KingMoveValidation.Invalid) != 0)
            {
              progress = ValidationProgress.Invalid;
              error = (ValidationError)kingMove;
              return;
            }

            hint = MoveHint.KingMove | (MoveHint)kingMove;
            if ((kingMove & KingMoveValidation.Castling) != 0)
            {
              if ((src._info.Castling & (CastlingAvailability)kingMove) == 0)
              {
                progress = ValidationProgress.Invalid;
                error = ValidationError.NoCastlingThisWay;
                return;
              }
              if (src._info.GameState == GameState.CheckToBlack ||
                  src._info.GameState == GameState.CheckToWhite)
              {
                progress = ValidationProgress.Invalid;
                error = ValidationError.CantCastleUnderAttack;
                return;
              }
            }
            break;
        }

        progress = ValidationProgress.Valid;

        _cells = new CompactBoard(src._cells);
      
        _cells[to] = piece;
        _cells[from] = CompactPiece.EmptyCell;

        _info.SideOnMove = src._info.SideOnMove == PieceColor.White
          ? PieceColor.Black : PieceColor.White;

        _info.HalfmoveClock =
          (hint & (MoveHint.Take | MoveHint.PawnMove)) != 0
          ? 0 : src._info.HalfmoveClock + 1;

        if (color == PieceColor.Black)
          _info.FullmoveNumber = src._info.FullmoveNumber + 1;

        _info.Castling = src._info.Castling;

        if ((hint & MoveHint.PawnDoubleMove) != 0)
        {
          _info.IsEnPassant = true;
          _info.EnPassant = from%8;
        }
        else if ((hint & MoveHint.EnPassant) != 0)
        {
          if (color == PieceColor.White)
            _cells[CompactBoard.A5 + src._info.EnPassant] = CompactPiece.EmptyCell;
          else
            _cells[CompactBoard.A4 + src._info.EnPassant] = CompactPiece.EmptyCell;
        }
        else if (hint == MoveHint.WhiteKingCastling)
        {
          _cells[CompactBoard.H1] = CompactPiece.EmptyCell;
          _cells[CompactBoard.F1] = CompactPiece.WhiteRook;
          _info.Castling &= ~CastlingAvailability.White;
        }
        else if (hint == MoveHint.WhiteQueenCastling)
        {
          _cells[CompactBoard.A1] = CompactPiece.EmptyCell;
          _cells[CompactBoard.D1] = CompactPiece.WhiteRook;
          _info.Castling &= ~CastlingAvailability.White;
        }
        else if (hint == MoveHint.BlackKingCastling)
        {
          _cells[CompactBoard.H8] = CompactPiece.EmptyCell;
          _cells[CompactBoard.F8] = CompactPiece.BlackRook;
          _info.Castling &= ~CastlingAvailability.Black;
        }
        else if (hint == MoveHint.BlackQueenCastling)
        {
          _cells[CompactBoard.A8] = CompactPiece.EmptyCell;
          _cells[CompactBoard.D8] = CompactPiece.BlackRook;
          _info.Castling &= ~CastlingAvailability.Black;
        }
        else if (hint == MoveHint.RookMove)
        {
          switch (from)
          {
            case CompactBoard.A1:
              _info.Castling &= ~CastlingAvailability.WhiteQueen;
              break;
            case CompactBoard.H1:
              _info.Castling &= ~CastlingAvailability.WhiteKing;
              break;
            case CompactBoard.A8:
              _info.Castling &= ~CastlingAvailability.BlackQueen;
              break;
            case CompactBoard.H8:
              _info.Castling &= ~CastlingAvailability.BlackKing;
              break;
          }
        }
      }
      finally
      {
        move.Hint = hint;
        move.ValidationProgress = progress;
        move.Warning = warnins;
        move.Error = error;
        PrevMove = move;
        Prev = src;
      }
    }

    #endregion

    #region ' FEN '

    private Board() { }

    /// <summary> Load position from FEN string </summary>
    /// <param name="fen">FEN string</param>
    /// <remarks>http://en.wikipedia.org/wiki/Forsyth%E2%80%93Edwards_Notation</remarks>
    public static Board FromFenString(string fen)
    {
      var offset = 0;
      var board = new Board();
      try
      {
        board.LoadPiecePlacementSection(fen, ref offset);
        board.LoadActiveColorSection(fen, ref offset);
        board.LoadCastlingAvailabilitySection(fen, ref offset);
        board.LoadEnPassantSection(fen, ref offset);
        board.LoadHalfmoveClockSection(fen, ref offset);
        board.LoadFullmoveNumberSection(fen, ref offset);
      }
      catch (IndexOutOfRangeException x)
      {
        throw new FormatException("Unexpected end of FEN string", x);
      }
      return board;
    }

    private void LoadPiecePlacementSection(string fen, ref int i)
    {
      for (var sq = 63; ; i++)
      {
        if (fen[i] == ' ') break;
        if (fen[i] >= '1' && fen[i] <= '9') sq -= fen[i] - '0';
        else if ('/' == fen[i]) { }
        else
        {
          _cells[(sq / 8) * 8 + 7 - sq % 8] = Piece.Parse(fen[i]);
          sq--;
        }
      }
      i++; // Skip the space
    }
    private void LoadActiveColorSection(string fen, ref int i)
    {
      _info.SideOnMove = Color.Parse(fen[i++]);
      if (fen[i] != ' ')
        throw new FormatException("Unexpected symbol");
      i++; // skip the space 
    }
    private void LoadCastlingAvailabilitySection(string fen, ref int i)
    {
      var flags = default(CastlingAvailability);
      for (; ; i++)
      {
        if (fen[i] == '-') { }
        else if (fen[i] == 'K') flags |= CastlingAvailability.WhiteKing;
        else if (fen[i] == 'Q') flags |= CastlingAvailability.WhiteQueen;
        else if (fen[i] == 'k') flags |= CastlingAvailability.BlackKing;
        else if (fen[i] == 'q') flags |= CastlingAvailability.BlackQueen;
        else if (fen[i] == ' ') break;
        else throw new FormatException("illegal character");
      }
      _info.Castling = flags;
      i++; // Skip the space
    }
    private void LoadEnPassantSection(string fen, ref int i)
    {
      if (fen[i] != '-')
      {
        _info.EnPassant = EnPassant.Parse(fen[i]);
        _info.IsEnPassant = true;
      }
      i++; // Skip the rank, or skip the '-'
      i++; // Skip the space
    }
    private void LoadHalfmoveClockSection(string fen, ref int i)
    {
      var res = 0;
      for (; ; i++)
      {
        if (fen[i] == ' ') break;
        if (fen[i] >= '0' && fen[i] <= '9')
          res = res * 10 + fen[i] - '0';
      }
      _info.HalfmoveClock = res;
      i++; // Skip the space
    }
    private void LoadFullmoveNumberSection(string fen, ref int i)
    {
      var res = 0;
      for (; i < fen.Length; i++)
        if (fen[i] >= '0' && fen[i] <= '9')
          res = res * 10 + fen[i] - '0';
      _info.FullmoveNumber = res;
    }

    public string ToFenString()
    {
      var fen = new StringBuilder(77);
      for (int empty = 0, sq = 63; sq >= 0; sq--)
      {
        var idx = (sq / 8) * 8 + 7 - sq % 8;
        if (_cells[idx] == CompactPiece.EmptyCell)
        {
          empty++;
          if (0 == sq % 8)
          {
            if (empty != 0) fen.Append((char)('0' + empty));
            if (sq != 0) fen.Append('/');
            empty = 0;
          }
          continue;
        }

        if (empty != 0) fen.Append((char)('0' + empty));
        fen.Append(Piece.Unpack(_cells[idx]).Symbol);
        empty = 0;
        if (sq != 0 && sq % 8 == 0) fen.Append('/');
      }

      fen.Append(' ');
      fen.Append(_info.SideOnMove == PieceColor.White ? 'w' : 'b');

      fen.Append(' ');
      var castling = _info.Castling;
      if (castling == CastlingAvailability.None)
      {
        fen.Append('-');
      }
      else
      {
        if ((castling & CastlingAvailability.WhiteKing) != 0) fen.Append('K');
        if ((castling & CastlingAvailability.WhiteQueen) != 0) fen.Append('Q');
        if ((castling & CastlingAvailability.BlackKing) != 0) fen.Append('k');
        if ((castling & CastlingAvailability.BlackQueen) != 0) fen.Append('q');
      }

      fen.Append(' ');
      if (!_info.IsEnPassant)
      {
        fen.Append('-');
      }
      else
      {
        fen.Append("abcdefgh"[_info.EnPassant]);
        fen.Append(_info.SideOnMove == PieceColor.White ? '6' : '3');
      }

      fen.Append(' ');
      fen.Append(_info.HalfmoveClock);

      fen.Append(' ');
      fen.Append(_info.FullmoveNumber);

      return fen.ToString();
    }

    #endregion
  }
}

